﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using JetBlack.Diagnostics;
using JetBlack.FeedBus.Messages;
using JetBlack.IO;
using JetBlack.Net.Sockets;

namespace JetBlack.FeedBus.Distributor
{
    internal class Interactor : IDisposable, IComparable<Interactor>, IEquatable<Interactor>
    {
        internal event Action<Interactor> OnClosed;
        internal event Action<Interactor, Message> OnMessageReceived;
        internal event Action<Interactor> OnWriteRequest;

        internal readonly int Id;
        internal readonly Socket Socket;
        private readonly NonBlockingReader reader;
        private readonly NonBlockingWriter writer;

        internal Interactor(Socket socket, int id)
        {
            Socket = socket;
            reader = new NonBlockingReader(socket);
            writer = new NonBlockingWriter(socket);
            Id = id;
        }

        internal void ReadMessage()
        {
            try
            {
                if (reader.Read())
                {
                    byte[] byteArray = reader.Pop();
                    MemoryStream memoryStream = new MemoryStream(byteArray);
                    Message message = Message.Read(memoryStream);
                    if (OnMessageReceived != null)
                        OnMessageReceived(this, message);
                }
            }
            catch (System.IO.EndOfStreamException)
            {
                RaiseOnClosed();
            }
            catch (IOException ex)
            {
                if (ex.InnerException == null ||
                    !(ex.InnerException is SocketException) ||
                    ((SocketException)ex.InnerException).ErrorCode != 10060)
                {
                    AppEnvironment.TraceSource.Verbose("Interactor disconnected: {0}", this);
                    RaiseOnClosed();
                }
            }
            catch
            {
                AppEnvironment.TraceSource.Verbose("Interactor disconnected: {0}", this);
                RaiseOnClosed();
            }
        }

        internal void WriteMessage()
        {
            if (!writer.Write())
                RaiseOnWriteRequest();
        }

        internal void SendMessage(Message message)
        {
            MemoryStream memoryStream = new MemoryStream();
            message.Write(memoryStream);
            byte[] byteArray = memoryStream.ToArray();
            writer.Push(byteArray);
            RaiseOnWriteRequest();
        }

        internal bool HasMessageToWrite
        {
            get { return !writer.IsEmpty; }
        }

        private void RaiseOnClosed()
        {
            if (OnClosed != null)
                OnClosed(this);
        }

        private void RaiseOnWriteRequest()
        {
            if (OnWriteRequest != null)
                OnWriteRequest(this);
        }

        internal IPEndPoint LocalEndPoint
        {
            get { return (System.Net.IPEndPoint)Socket.LocalEndPoint; }
        }

        internal IPEndPoint RemoteEndPoint
        {
            get { return (System.Net.IPEndPoint)Socket.RemoteEndPoint; }
        }

        public override string ToString()
        {
            return string.Format("{0}/{1}", Id, RemoteEndPoint);
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Interactor);
        }

        public bool Equals(Interactor other)
        {
            return other != null && other.Id == Id;
        }

        public int CompareTo(Interactor other)
        {
            if (other == null)
                return 1;
            else
                return Id.CompareTo(other.Id);
        }

        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        public void Dispose()
        {
            Socket.Close();
        }
    }
}
